package belf.migrate.core.jdbc;

import belf.migrate.core.exception.ParseMappingFileError;
import belf.migrate.core.util.ConfigUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 保存源数据库到目标数据库的函数映射关系。参考映射定义文件conf/db-function-mapping/MYSQL-DAMENG.xml。<br/>
 * 下面的规则表示源和目标库函数名一致：
 * <pre>
 *     &lt;mapping from="avg" to="avg"/&gt;
 * </pre>
 * 下面的规则表示源数据库的库函数，在目标库没有对应的实现：
 * <pre>
 *     &lt;mapping from="group_concat" to=""/&gt;
 * </pre>
 */
public class DBFunctionMapping {
    private static final Logger log = LoggerFactory.getLogger(DBTypeMapping.class);

    private final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

    private static final DBFunctionMapping instance = new DBFunctionMapping();

    private final Map<String, Map<String, FunctionModel>> mappings = new HashMap<>();

    private DBFunctionMapping() {
        File path = new File(ConfigUtil.getDBFunctionMappingDir());
        Arrays.stream(
                        path.list())
                .filter(name-> name.endsWith(".xml"))
                .forEach(s -> {
                    try {
                        parseMapping(path.getAbsolutePath() + File.separator + s);
                    } catch (ParseMappingFileError e) {
                        e.printStackTrace();
                        log.error("Parse DB Type Mapping error: {}", s);
                    }

                });

    }

    public static DBFunctionMapping getInstance() {
        return instance;
    }

    /**
     * 根据mapping文件中定义的“id”进行查询，id一般为：Source Identifier-Sink Identifier
     * @param identifier
     * @return
     */
    public Map<String, FunctionModel> getMapping(String identifier) {
        return mappings.get(identifier);
    }

    private void parseMapping(String filename) throws ParseMappingFileError {
        log.info("parse mapping: {}", filename);

        try {
            DocumentBuilder domBuilder = documentBuilderFactory.newDocumentBuilder();
            Document doc = domBuilder.parse(new InputSource(new InputStreamReader(new FileInputStream(filename), "utf-8")));
            Element root = doc.getDocumentElement();
            String identifier = root.getAttribute("id").toUpperCase();

            Map<String, FunctionModel> fieldsMap = new HashMap<>();
            NodeList nodes = root.getChildNodes();
            if (nodes != null) {
                for (int i = 0; i < nodes.getLength(); i++) {
                    Node child = nodes.item(i);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        Element childElement = (Element) child;
                        String to = childElement.getAttribute("to").trim();
                        String level = childElement.getAttribute("level");
                        String description = childElement.getAttribute("desc");
                        FunctionModel functionModel = new FunctionModel(to, level, description);
                        fieldsMap.put(childElement.getAttribute("from").trim().toUpperCase(),
                                functionModel);
                    }
                }
            }

            mappings.put(identifier, fieldsMap);

        } catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
            throw new ParseMappingFileError(e);
        }

    }

}
